package com.dingreading.cloud.common.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * HTTP请求工具类。
 */
public final class HttpUtils {

    private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);

    /**
     * 让工具类彻底不可以实例化
     */
    private HttpUtils() {
        throw new Error("工具类不可以实例化");
    }

    /**
     * 发送POST请求。
     *
     * @param url    访问url
     * @param params 访问参数
     * @return
     * @version 1.0.0
     * @since 1.0.0
     */
    public static String post(String url, Map<String, String> params) {
        // CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpClient httpClient = HttpUtils.createSSLClientDefault();

        List<NameValuePair> nvps = new ArrayList<>();
        if (null != params) {
            for (Entry<String, String> entry : params.entrySet()) {
                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }

        HttpPost post = new HttpPost(url);

        post.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));

        String data = "";

        try (CloseableHttpResponse response = httpClient.execute(post)) {

            HttpEntity entity = response.getEntity();
            InputStream in = entity.getContent();
            data = HttpUtils.readResponse(in);

            // 消耗掉response
            EntityUtils.consume(entity);
        } catch (Exception e) {
            logger.error("发送HTTP POST请求异常", e);
        }

        return data;
    }

    /**
     * 发送POST请求。
     *
     * @param url    访问url
     * @param params 访问参数
     * @return
     * @version 1.0.0
     * @since 1.0.0
     */
    public static String post(String url, String params) {
        CloseableHttpClient httpClient = HttpUtils.createSSLClientDefault();

        HttpPost post = new HttpPost(url);
        post.setEntity(new StringEntity(params, Consts.UTF_8));

        String data = "";

        try (CloseableHttpResponse response = httpClient.execute(post)) {

            HttpEntity entity = response.getEntity();
            InputStream in = entity.getContent();
            data = HttpUtils.readResponse(in);

            // 消耗掉response
            EntityUtils.consume(entity);
        } catch (Exception e) {
            logger.error("发送HTTP POST请求异常", e);
        }

        return data;
    }


    /**
     * 微信小程序发送POST请求。
     *
     * @param url    访问url
     * @param params 访问参数
     * @return
     * @version 1.0.0
     * @since 1.0.0
     */
    public static JSONObject xcxBytesPost(String url, String params) {
        CloseableHttpClient httpClient = HttpUtils.createSSLClientDefault();

        HttpPost post = new HttpPost(url);
        post.setEntity(new StringEntity(params, Consts.UTF_8));

        JSONObject jsonObject = null;

        try (CloseableHttpResponse response = httpClient.execute(post)) {
            HttpEntity entity = response.getEntity();
            Header contentType = entity.getContentType();
            String value = contentType.getValue();
            jsonObject = new JSONObject();
            if (value.contains("json")) {
                // 返回的是json字符串
                InputStream in = entity.getContent();
                String data = HttpUtils.readResponse(in);

                jsonObject.put("status", 500);
                jsonObject.put("data", data);
            } else if (value.contains("image")) {
                // 返回的是图片字节流
                byte[] buffer = EntityUtils.toByteArray(entity);

                jsonObject.put("status", 0);
                jsonObject.put("data", buffer);
            }

            // 消耗掉response
            EntityUtils.consume(entity);
        } catch (Exception e) {
            logger.error("发送HTTP POST请求异常", e);
        }

        return jsonObject;
    }

    /**
     * 发送GET请求。
     *
     * @param url 访问url
     * @return
     * @version 1.0.0
     * @since 1.0.0
     */
    public static String get(String url) {
        // CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpClient httpClient = HttpUtils.createSSLClientDefault();

        HttpGet get = new HttpGet(url);

        String data = "";

        try (CloseableHttpResponse response = httpClient.execute(get)) {

            HttpEntity entity = response.getEntity();
            InputStream in = entity.getContent();
            data = HttpUtils.readResponse(in);

            // 消耗掉response
            EntityUtils.consume(entity);
        } catch (Exception e) {
            logger.error("发送HTTP GET请求异常", e);
        }

        return data;
    }

    /**
     * 读取响应。
     *
     * @param in {@linkplain InputStream}
     * @return 响应内容。
     * @version 1.0.0
     * @since 1.0.0
     */
    public static String readResponse(InputStream in) {
        StringBuilder data = new StringBuilder();

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
            String line = null;
            while ((line = reader.readLine()) != null) {
                data.append(line).append('\n');
            }
        } catch (Exception e) {
            logger.error("读取HTTP响应结果异常", e);
        }

        return data.toString();
    }

    /**
     * 创建 SSL HTTP Client。
     *
     * @return SSL HTTP Client
     * @version 1.0.0
     * @since 1.0.0
     */
    public static CloseableHttpClient createSSLClientDefault() {
        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                // 信任所有
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();

            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);

            return HttpClients.custom().setSSLSocketFactory(sslsf).build();
        } catch (KeyManagementException e) {
            logger.error(e.getMessage(), e);
        } catch (NoSuchAlgorithmException e) {
            logger.error(e.getMessage(), e);
        } catch (KeyStoreException e) {
            logger.error(e.getMessage(), e);
        }

        return HttpClients.createDefault();
    }

    /**
     * Post String
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method,
                                      Map<String, String> headers,
                                      Map<String, String> querys,
                                      String body)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }

    /**
     * Post stream
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method,
                                      Map<String, String> headers,
                                      Map<String, String> querys,
                                      byte[] body)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * get
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public static String doGet(String host, String path, String method,
                               Map<String, String> headers,
                               Map<String, String> querys)
            throws Exception {
        CloseableHttpClient httpClient = HttpUtils.createSSLClientDefault();

        HttpGet request = new HttpGet(buildUrl(host, path, querys));
        for (Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        String data = "";

        try (CloseableHttpResponse response = httpClient.execute(request)) {

            HttpEntity entity = response.getEntity();
            InputStream in = entity.getContent();
            data = HttpUtils.readResponse(in);

            // 消耗掉response
            EntityUtils.consume(entity);
        } catch (Exception e) {
            logger.error("发送HTTP GET请求异常", e);
        }

        return data;
    }

    private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(host);
        if (!StringUtils.isBlank(path)) {
            sbUrl.append(path);
        }
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }

        return sbUrl.toString();
    }

    private static HttpClient wrapClient(String host) {
        HttpClient httpClient = new DefaultHttpClient();
        if (host.startsWith("https://")) {
            sslClient(httpClient);
        }

        return httpClient;
    }

    private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] xcs, String str) {

                }

                public void checkServerTrusted(X509Certificate[] xcs, String str) {

                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * url资源 转 byte[]
     *
     * @param path
     * @return
     * @throws IOException
     */
    public static byte[] url2byte(String path) throws IOException {
        byte[] data = null;
        URL url = null;
        InputStream input = null;
        try {
            url = new URL(path);
            HttpURLConnection httpUrl = (HttpURLConnection) url.openConnection();
            httpUrl.connect();
            httpUrl.getInputStream();
            input = httpUrl.getInputStream();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int numBytesRead = 0;
        while ((numBytesRead = input.read(buf)) != -1) {
            output.write(buf, 0, numBytesRead);
        }
        data = output.toByteArray();
        output.close();
        input.close();
        return data;
    }

}
